package com.chinaunicom.emergency.service;

import com.chinaunicom.emergency.model.Lock;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author liliang
 * @date 2019-12-13
 */
@Log4j2
@Service
public class RedisService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisTemplate<String, Object> redisCacheTemplate;

    public void putString(String key,String value) {
        stringRedisTemplate.opsForValue().set(key, value);
    }

    public String getString(String key){
        final String v1 = stringRedisTemplate.opsForValue().get(key);
        log.info("[字符缓存结果] - [{}]", v1);
        return v1;
    }

    public void putObject(String key,Object value) {
        //以下只演示整合，具体Redis命令可以参考官方文档，Spring Data Redis 只是改了个名字而已，Redis支持的命令它都支持
//        String key = "carry:user:1";
        ValueOperations<String, Object> valueOperations = redisCacheTemplate.opsForValue();
        valueOperations.set(key, value);
    }

    public <V> V getObject(String key){
        ValueOperations<String, Object> valueOperations = redisCacheTemplate.opsForValue();
        return (V)valueOperations.get(key);
    }

    public void putList(String key,Object value) {
        ListOperations<String, Object> listOperations = redisCacheTemplate.opsForList();
        listOperations.rightPush(key, value);
    }

    public List getList(String key){
        ListOperations<String, Object> listOperations = redisCacheTemplate.opsForList();
        return listOperations.range(key, 0, -1);
    }

    public void putHash(String hkey,Object key,Object value) {
        HashOperations<String, Object, Object> hashOperations = redisCacheTemplate.opsForHash();
        hashOperations.put(hkey, key, value);
    }

    public Map getHash(String hkey){
        HashOperations<String, Object, Object> hashOperations = redisCacheTemplate.opsForHash();
        Map<Object, Object> map = hashOperations.entries(hkey);
        return map;
    }
    public void putHash(String hkey,Map map) {
        HashOperations<String, Object, Object> hashOperations = redisCacheTemplate.opsForHash();
        hashOperations.putAll(hkey, map);
    }

    public <V> V getHash(String hkey,Object key){
        return (V)getHash(hkey).get(key);
    }

    public void putSet(String key,Object... value) {
        SetOperations<String, Object> setOperations = redisCacheTemplate.opsForSet();
        setOperations.add(key, value);
    }

    public Iterator getSet(String key){
        SetOperations<String, Object> setOperations = redisCacheTemplate.opsForSet();
        Cursor<Object> cursor = setOperations.scan(key, ScanOptions.NONE);
        return cursor;
    }



    public long putZset(String key, Set<ZSetOperations.TypedTuple<Object>> tuples) {
        ZSetOperations<String, Object> zsetOperations = redisCacheTemplate.opsForZSet();
        return zsetOperations.add(key,tuples);
    }
    public Set<Object> getZset(String key, int start, int end){
        //降序
        ZSetOperations<String, Object> zsetOperations = redisCacheTemplate.opsForZSet();
        Set<Object> zset = zsetOperations.reverseRange(key,start,end);
        return zset;
    }
    public long getZsetZcard(String key){
        //降序
        ZSetOperations<String, Object> zsetOperations = redisCacheTemplate.opsForZSet();
        long zCard = zsetOperations.zCard(key);
        return zCard;
    }


    public boolean deleteKey(String key){
        try{
            boolean bln = hasKey(key);
            if (bln){
                return redisCacheTemplate.delete(key) || stringRedisTemplate.delete(key);//根据key删除缓存
            }
            else {
                return false;
            }
        }
        catch (Exception ex){
            log.info("deleteKey",ex);
            return false;
        }
    }

    public Long deleteHashFields(String key,Object... fields){
        try{
            boolean bln = hasKey(key);
            if (bln){
                HashOperations<String, Object, Object> hashOperations = redisCacheTemplate.opsForHash();
                return hashOperations.delete(key,fields);
            }
            else {
                return null;
            }
        }
        catch (Exception ex){
            log.info("deleteHashFields",ex);
            return null;
        }
    }

    public boolean hasKey(String key) {
        return stringRedisTemplate.hasKey(key) || redisCacheTemplate.hasKey(key);
    }

    public boolean hasHashField(String key,String field) {
        HashOperations<String, Object, Object> hashOperations = redisCacheTemplate.opsForHash();
        return redisCacheTemplate.hasKey(key) && hashOperations.hasKey(key,field);
    }















    /**
     * 单个业务持有锁的时间2s，防止死锁
     */
    private final static long LOCK_EXPIRE = 2 * 1000L;
    /**
     * 默认30ms尝试一次
     */
    private final static long LOCK_TRY_INTERVAL = 30L;
    /**
     * 默认尝试10s
     */
    private final static long LOCK_TRY_TIMEOUT = 10 * 1000L;



    /**
     * 尝试获取全局锁
     *
     * @param lock 锁的名称
     * @return true 获取成功，false获取失败
     */
    public boolean tryLock(Lock lock) {
        return getLock(lock, LOCK_TRY_TIMEOUT, LOCK_TRY_INTERVAL, LOCK_EXPIRE);
    }

    /**
     * 尝试获取全局锁
     * SETEX：可以设置超时时间
     * @param lock    锁的名称
     * @param timeout 获取超时时间 单位ms
     * @return true 获取成功，false获取失败
     */
    public boolean tryLock(Lock lock, long timeout) {
        return getLock(lock, timeout, LOCK_TRY_INTERVAL, LOCK_EXPIRE);
    }

    /**
     * 尝试获取全局锁
     *
     * @param lock        锁的名称
     * @param timeout     获取锁的超时时间
     * @param tryInterval 多少毫秒尝试获取一次
     * @return true 获取成功，false获取失败
     */
    public boolean tryLock(Lock lock, long timeout, long tryInterval) {
        return getLock(lock, timeout, tryInterval, LOCK_EXPIRE);
    }

    /**
     * 尝试获取全局锁
     *
     * @param lock           锁的名称
     * @param timeout        获取锁的超时时间
     * @param tryInterval    多少毫秒尝试获取一次
     * @param lockExpireTime 锁的过期
     * @return true 获取成功，false获取失败
     */
    public boolean tryLock(Lock lock, long timeout, long tryInterval, long lockExpireTime) {
        return getLock(lock, timeout, tryInterval, lockExpireTime);
    }


    /**
     * 操作redis获取全局锁
     *
     * @param lock           锁的名称
     * @param timeout        获取的超时时间
     * @param tryInterval    多少ms尝试一次
     * @param lockExpireTime 获取成功后锁的过期时间
     * @return true 获取成功，false获取失败
     */
    public boolean getLock(Lock lock, long timeout, long tryInterval, long lockExpireTime) {

        try {
            if (StringUtils.isEmpty(lock.getName()) || StringUtils.isEmpty(lock.getValue())) {
                return false;
            }
            long startTime = System.currentTimeMillis();
            do {
                if (!redisCacheTemplate.hasKey(lock.getName())) {
                    ValueOperations<String, Object> ops = redisCacheTemplate.opsForValue();
                    ops.set(lock.getName(), lock.getValue(), lockExpireTime, TimeUnit.MILLISECONDS);
                    return true;
                } else {
                    //存在锁
                    log.debug("lock is exist!！！");
                }

                //尝试超过了设定值之后直接跳出循环
                if (System.currentTimeMillis() - startTime > timeout) {
                    return false;
                }

                //每隔多长时间尝试获取
                Thread.sleep(tryInterval);
            }
            while (redisCacheTemplate.hasKey(lock.getName()));
        } catch (InterruptedException e) {
            log.error(e.getMessage());
            return false;
        }
        return false;
    }

    /**
     * 获取锁
     * SETNX(SET If Not Exists)：当且仅当 Key 不存在时，则可以设置，否则不做任何动作。
     */
    public Boolean getLockNoTime(Lock lock) {
        if (!StringUtils.isEmpty(lock.getName())) {
            return false;
        }

        // setIfAbsent 底层封装命令 是 setNX()
        boolean falg = redisCacheTemplate.opsForValue().setIfAbsent(lock.getName(), lock.getValue());

        return false;
    }

    /**
     * 释放锁
     */
    public void releaseLock(Lock lock) {
        if (!StringUtils.isEmpty(lock.getName())) {
            redisCacheTemplate.delete(lock.getName());
        }
    }
}
